package com.fdd.op.sdk;

import com.fdd.op.sdk.constants.Constants;
import com.fdd.op.sdk.internal.mapping.RequestField;
import com.fdd.op.sdk.internal.parser.json.ObjectJsonParser;
import com.fdd.op.sdk.internal.token.FddTokenStore;
import com.fdd.op.sdk.internal.util.*;

import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.net.URL;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 基于REST的Fdd客户端
 *
 * @author Fadada
 * @date 2021/04/26
 */
public class DefaultFddClient extends BaseClient implements FddClient {
    /**
     * 缓存token，默认内存
     */
    private FddTokenStore tokenStore;
    /**
     * 使用默认容量创建一个Map
     */
    private static Map<String, Integer> reqCount = new ConcurrentHashMap<>(Constants.DEFAULT_CAPACITY);
    /**
     * 请求参数
     */
    protected String serverUrl;
    protected String signMethod = Constants.SIGN_METHOD_SHA256;
    protected String authType = Constants.AUTH_SIGN;
    /**
     * 默认连接超时时间为15秒
     */
    protected int connectTimeout = 15000;
    /**
     * 默认响应超时时间为30秒
     */
    protected int readTimeout = 30000;
    /**
     * 是否在客户端校验请求
     */
    protected boolean needCheckRequest = true;
    /**
     * 是否对响应结果进行解释
     */
    protected boolean needEnableParser = true;
    /**
     * 是否采用精简化的JSON返回
     */
    protected boolean useSimplifyJson = true;
    /**
     * 是否启用响应GZIP压缩
     */
    protected boolean useGzipEncoding = true;
    /**
     * 是否启用了httpdns
     */
    private boolean isHttpDnsEnabled = false;
    /**
     * 原始请求Host
     */
    private String originalHttpHost = null;

    public DefaultFddClient(String serverUrl, String appId, String appKey) {
        super(appId, appKey);
        this.serverUrl = serverUrl;
    }

    public DefaultFddClient(String serverUrl, String appId, String appKey, int connectTimeout, int readTimeout) {
        this(serverUrl, appId, appKey);
        this.connectTimeout = connectTimeout;
        this.readTimeout = readTimeout;
    }

    public DefaultFddClient(String serverUrl, String appId, String appKey, String authType, int connectTimeout, int readTimeout) {
        this(serverUrl, appId, appKey);
        this.authType = authType;
        this.connectTimeout = connectTimeout;
        this.readTimeout = readTimeout;
    }

    public DefaultFddClient(String serverUrl, String appId, String appKey, int connectTimeout, int readTimeout, String signMethod) {
        this(serverUrl, appId, appKey, connectTimeout, readTimeout);
        this.signMethod = signMethod;
    }

    public DefaultFddClient tokenStore(FddTokenStore tokenStore) {
        this.tokenStore = tokenStore;
        return this;
    }

    @Override
    public <T extends FddResponse> T execute(FddRequest<T> request) throws ApiException {
        return execute(request, null);
    }

    @Override
    public <T extends FddResponse> T execute(FddRequest<T> request, String session) throws ApiException {
        return doExecute(request, session);
    }

    private <T extends FddResponse> T doExecute(FddRequest<T> request, String session) throws ApiException {
        long start = System.currentTimeMillis();
        // 构建响应解释器
        FddParser<T> parser = null;
        if (this.needEnableParser) {
            parser = new ObjectJsonParser<T>(request.getResponseClass(), this.useSimplifyJson);
        }
        // 本地校验请求参数
        if (this.needCheckRequest) {
            try {
                request.check();
            } catch (ApiRuleException e) {
                T localResponse;
                try {
                    localResponse = request.getResponseClass().newInstance();
                } catch (Exception xe) {
                    throw new ApiException(xe);
                }
                localResponse.setCode(e.getErrCode());
                localResponse.setMessage(e.getErrMsg());
                return localResponse;
            }
        }
        RequestParametersHolder requestHolder = new RequestParametersHolder();
        FddHashMap appParams;
        if(request.getTextParams() == null){
            appParams = new FddHashMap();
        }else{
            appParams = new FddHashMap(request.getTextParams());
        }

        Field[] fields = request.getClass().getDeclaredFields();
        for (Field field : fields) {
            if ((!Modifier.isPublic(field.getModifiers()) || !Modifier.isPublic(field.getDeclaringClass().getModifiers())) && !field.isAccessible()) {
                field.setAccessible(true);
            }
            RequestField requestField = field.getAnnotation(RequestField.class);
            if(requestField != null){
                Object value = null;
                try {
                    value = field.get(request);
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
                if (value == null) {
                    continue;
                }
                appParams.put(field.getName(), value);
            }
        }

        requestHolder.setApplicationParams(appParams);
        //增加参数
        FddHashMap protocalOptParams = new FddHashMap();
        protocalOptParams.put(Constants.SESSION, session);
        if (this.useSimplifyJson) {
            protocalOptParams.put(Constants.SIMPLIFY, Boolean.TRUE.toString());
        }
        requestHolder.setProtocalOptParams(protocalOptParams);
        //报文参数
        FddHashMap headParams = new FddHashMap(request.getHeaderMap());
        //时间戳
        Long timestamp = request.getTimestamp();
        if (timestamp == null) {
            timestamp = System.currentTimeMillis();
        }
        //判断那种协议
        if (Constants.AUTH_SIGN.equals(this.authType)) {
            // 添加协议级请求参数
            FddHashMap protocalMustParams = new FddHashMap();
            protocalMustParams.put(Constants.VERSION, "2.0");
            protocalMustParams.put(Constants.APP_ID, this.getAppId());
            protocalMustParams.put(Constants.TIMESTAMP, Constants.DATEFORMAT.format(new Date(timestamp)));
            protocalMustParams.put(Constants.SIGN_METHOD, signMethod);
            requestHolder.setProtocalMustParams(protocalMustParams);
            //head
            headParams.put(Constants.VERSION, protocalMustParams.get(Constants.VERSION));
            headParams.put(Constants.APP_ID, protocalMustParams.get(Constants.APP_ID));
            headParams.put(Constants.TIMESTAMP, protocalMustParams.get(Constants.TIMESTAMP));
            headParams.put(Constants.SIGN_METHOD, protocalMustParams.get(Constants.SIGN_METHOD));
            // 添加签名参数
            String sign = SignatureUtils.getSign(headParams, this.getApiKey());
            if (request instanceof FddUploadRequest) {
                sign = SignatureUtils.getSign(headParams, this.getApiKey());
            }else{
                sign = SignatureUtils.getSign(requestHolder.getAllParams(), this.getApiKey());
            }
            protocalMustParams.put(Constants.SIGN, sign);
            headParams.put(Constants.SIGN, protocalMustParams.get(Constants.SIGN));
        } else {
            String accessToken = new AccessTokenClient(this.serverUrl, this.getAppId(), this.getApiKey()).tokenStore(tokenStore).getAccessToken();
            headParams.put(Constants.OAUTH_AUTHORIZATION, Constants.OAUTH_BEARER + accessToken);
            //System.out.println("============header:" + Constants.OAUTH_BEARER + accessToken);
        }
        try {
            String realServerUrl = FddUrlUtils.getServerUrl(this.serverUrl, request.getApiMethodName(), session, appParams);
            String fullUrl = WebUtils.buildRequestUrl(realServerUrl);
            String rsp;
            // 是否需要压缩响应
            if (this.useGzipEncoding) {
                request.getHeaderMap().put(Constants.ACCEPT_ENCODING, Constants.CONTENT_ENCODING_GZIP);
            }
            if (getHttpDnsHost() != null) {
                request.getHeaderMap().put(Constants.HTTP_DNS_HOST, getHttpDnsHost());
            }
            if (Constants.METHOD_POST.equals(request.getHttpMethod())) {
                // 是否需要上传文件
                if (request instanceof FddUploadRequest) {
                    FddUploadRequest<T> uRequest = (FddUploadRequest<T>) request;
                    Map<String, FileItem> fileParams = FddUtils.cleanupMap(uRequest.getFileParams());
                    if(fileParams != null){
                        rsp = WebUtils.doPost(fullUrl, appParams, fileParams, Constants.CHARSET_UTF8, connectTimeout, readTimeout, headParams);
                    }else{
                        rsp = WebUtils.doPost(fullUrl, appParams, Constants.CHARSET_UTF8,  connectTimeout, readTimeout, headParams, null);
                    }
                } else {
                    rsp = WebUtils.doPostWithJson(fullUrl, appParams, Constants.CHARSET_UTF8, connectTimeout, readTimeout, headParams);
                }
            } else {
                rsp = WebUtils.doGet(fullUrl, appParams, headParams);
            }
            //System.out.println("resp:"+rsp);
            requestHolder.setResponseBody(rsp);
        } catch (IOException e) {
            FddLogger.logApiError(this.getAppId(), request.getApiMethodName(), serverUrl, requestHolder.getAllParams(), System.currentTimeMillis() - start, e.toString());
            throw new ApiException(e);
        } catch (AccessTokenException e) {
            //令牌失效重试
            Integer count = reqCount.get(request.getHttpId());
            if (count == null) {
                count = 0;
            }
            if (count >= Constants.MONITOR_DURATION) {
                throw new ApiException(e);
            }
            reqCount.put(request.getHttpId(), count++);
            this.doExecute(request, session);
        }
        T tRsp;
        if (this.needEnableParser) {
            tRsp = parser.parse(requestHolder.getResponseBody());
            tRsp.setBody(requestHolder.getResponseBody());
        } else {
            try {
                tRsp = request.getResponseClass().newInstance();
                tRsp.setBody(requestHolder.getResponseBody());
            } catch (Exception e) {
                throw new ApiException(e);
            }
        }
        tRsp.setParams(appParams);
        if (!tRsp.isSuccess()) {
            FddLogger.logApiError(this.getAppId(), request.getApiMethodName(), serverUrl, requestHolder.getAllParams(), System.currentTimeMillis() - start, tRsp.getBody());
        }
        if (StringUtils.isEmpty(tRsp.getPath())) {
            tRsp.setPath(request.getApiMethodName());
        }
        return tRsp;
    }

    protected String getSdkVersion() {
        if (isHttpDnsEnabled) {
            return Constants.SDK_VERSION_HTTPDNS;
        }
        return Constants.SDK_VERSION;
    }

    /**
     * 是否在客户端校验请求参数。
     */
    public void setNeedCheckRequest(boolean needCheckRequest) {
        this.needCheckRequest = needCheckRequest;
    }

    /**
     * 是否把响应字符串解释为对象。
     */
    public void setNeedEnableParser(boolean needEnableParser) {
        this.needEnableParser = needEnableParser;
    }

    /**
     * 是否采用标准化的JSON格式返回。
     */
    public void setUseSimplifyJson(boolean useSimplifyJson) {
        this.useSimplifyJson = useSimplifyJson;
    }

    /**
     * 是否记录API请求错误日志。
     */
    public void setNeedEnableLogger(boolean needEnableLogger) {
        FddLogger.setNeedEnableLogger(needEnableLogger);
    }

    /**
     * 是否忽略HTTPS证书校验。
     */
    public void setIgnoreSSLCheck(boolean ignore) {
        WebUtils.setIgnoreSSLCheck(ignore);
    }

    /**
     * 是否启用响应GZIP压缩
     */
    public void setUseGzipEncoding(boolean useGzipEncoding) {
        this.useGzipEncoding = useGzipEncoding;
    }

    /**
     * 设置API请求的连接超时时间，默认为15秒。
     */
    public void setConnectTimeout(int connectTimeout) {
        this.connectTimeout = connectTimeout;
    }

    /**
     * 设置API请求的读超时时间，默认为30秒。
     */
    public void setReadTimeout(int readTimeout) {
        this.readTimeout = readTimeout;
    }

    public String getHttpDnsHost() {
        if (isHttpDnsEnabled) {
            return originalHttpHost;
        }
        return null;
    }

    /**
     * 启用http dns
     */
    public void enableHttpDns() {
        WebUtils.setIgnoreHostCheck(true);
        setHttpDnsHost(serverUrl);
        isHttpDnsEnabled = true;
    }


    /**
     * 沙箱环境启用http dns
     *
     * @param onlineAppId  正式id
     * @param onlineAppKey 正式环境key
     */
    public void enableHttpDns(String onlineAppId, String onlineAppKey) {
        WebUtils.setIgnoreHostCheck(true);
        setHttpDnsHost(serverUrl);
        isHttpDnsEnabled = true;
    }

    public void resetServerUrl(String serverUrl) {
        this.serverUrl = serverUrl;
    }

    public String getRequestUrl() {
        return this.serverUrl;
    }

    public void setAuthType(String authType) {
        this.authType = authType;
    }

    private void setHttpDnsHost(String serverUrl) {
        if (serverUrl == null || serverUrl.isEmpty()) {
            return;
        }
        try {
            URL url = new URL(serverUrl);
            originalHttpHost = url.getHost();
        } catch (Exception e) {
            throw new RuntimeException("error serverUrl:" + serverUrl, e);
        }
    }

}
